/*************************************************************
  Cocus Core - A simple PIC16 core emulator for PIC18
  
  version 2.0 by Santiago H. (santiagohssl@gmail.com)

  UNDER GNU LGPL

  file: vmasm.c
*************************************************************/

#include <p18cxxx.h>
#include "vmasm.h"
#include "vmcore.h"

/* *******************************************************************************
	ASM Emulation
 ******************************************************************************* */
 
void vm_emu_bsf_bcf(void *dev, vm_opcode * opcode, char set)
{
	vm_opcode op;
	op.packed16 = opcode->packed16 << 1;	// workaround for bit boundaries in C18
	vm_writeSingleBitExposed(dev, op.parse4op7f.f, op.parse4op7f.b, set);
}

void vm_emu_btfss_btfsc(void *dev, vm_opcode * opcode, char set)
{
	vm_opcode op;
	char 	membit;
	
	op.packed16 = opcode->packed16 << 1;	
	
	membit = vm_readSingleBitExposed(dev, op.parse4op7f.f, op.parse4op7f.b);
	
	if (membit)
	{
		if (set)
			// BTFSS and bit = 1, so, skip!
			vm_sumPC(dev);
	}
	else
		if (set == 0)
			// BTFSC and bit = 0, so, skip!
			vm_sumPC(dev);

}


void vm_emu_movwf(void *dev, vm_opcode * opcode)
{
	vm_opcode op;
	
	op.packed16 = opcode->packed16 << 1;
	vm_writeRamExposed(dev, op.parse7op7f.f, vm_getW(dev));
}

void vm_emu_swapf(void *dev, vm_opcode * opcode)
{
	vm_bytenibbles membyte, alterbyte;

	/* read the byte */
	membyte.ch = vm_readRamExposed(dev, opcode->parse6op7f.f); 

	/* swap nibbles */
	alterbyte.nibbles.n0 = membyte.nibbles.n1;
	alterbyte.nibbles.n1 = membyte.nibbles.n0;

	/* store result in W or ram */
	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, alterbyte.ch, opcode->parse6op7f.d);
}

void vm_emu_incfsz_decfsz(void *dev, vm_opcode * opcode, char sum)
{
	char membyte;
	
	membyte = vm_readRamExposed(dev, opcode->parse6op7f.f);

	if (sum)
		membyte++;
	else
		membyte--;
	
	if (membyte == 0)
		// f is 0 so we need to skip!
		vm_sumPC(dev);

	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, membyte, opcode->parse6op7f.d);
}

void vm_emu_goto(void *dev, vm_opcode * opcode)
{
	vm_intbytes pos;
	
	pos.bytes.low = opcode->parse3op11k.k0;
	pos.bytes.high = opcode->parse3op11k.k1;
	
	vm_setPC(dev, pos.data);
}

void vm_emu_call(void *dev, vm_opcode * opcode)
{
	vm_intbytes pos;

	char currstack;
	
	currstack = vm_getSelectedStack(dev);
	
	if (currstack == 8)
		// stack overflow!
		// as data is always circularly pushed on stack,
		// we have to go to the first stack id
		currstack = 0;

	/* push the current pc in the stack */
	vm_setSelectedStack(dev, ++currstack);
	vm_setStackPC(dev, vm_getPC(dev));

	/* set the call destination in pc */
	pos.bytes.low = opcode->parse3op11k.k0;
	pos.bytes.high = opcode->parse3op11k.k1;
	
	vm_setPC(dev, pos.data);
}

void vm_emu_return(void *dev)
{
	char currstack;

	currstack = vm_getSelectedStack(dev);
	
	if (currstack == 0)
		// stack underflow :S
		// as data is always circularly pushed on stack,
		// we have to go to the last stack id
		currstack = 9;

	vm_setPC(dev, vm_getStackPC(dev));
	vm_setSelectedStack(dev, --currstack);
}

void vm_emu_retfie(void *dev)
{
	vm_emu_return(dev);
	vm_intcon_set_gie(dev, 1);
}

void vm_emu_iorlw_xorlw(void *dev, vm_opcode * opcode, char i)
{
	char w;
	w = vm_getW(dev);
	
	if (i == 0)
		// XORLW
		w ^= opcode->parse6op8k.k;
	else
		// IORLW
		w |= opcode->parse6op8k.k;

	vm_setW(dev, w);
	vm_setZifZero(dev, w);		// just affect the Z flag
}

void vm_emu_andlw(void *dev, vm_opcode * opcode)
{
	char w;
	w = vm_getW(dev) & opcode->parse6op8k.k;
	vm_setW(dev, w);
	vm_setZifZero(dev, w);		// just affect the Z flag
}


void vm_emu_addlw(void *dev, vm_opcode * opcode)
{
	vm_intbytes sum;

	sum.bytes.low = vm_getW(dev);
	sum.data += opcode->parse5op8k.k;

	if(sum.bytes.high)
	{
		vm_status_set_c(dev,1);
		vm_status_set_dc(dev,1);
	}
	else
	{
		vm_status_set_c(dev,0);
		vm_status_set_dc(dev,0);
	}

	vm_setW(dev, sum.bytes.low);
	vm_setZifZero(dev, sum.bytes.low);
}

void vm_emu_sublw(void *dev, vm_opcode * opcode)
{
	char w;

	w = vm_getW(dev);

	if(w < opcode->parse5op8k.k)
	{
		vm_status_set_c(dev,1);
		vm_status_set_dc(dev,1);
	}
	else
	{
		vm_status_set_c(dev,0);
		vm_status_set_dc(dev,0);
	}

	vm_setW(dev, opcode->parse5op8k.k - w);
	vm_setZifZero(dev, opcode->parse5op8k.k - w);
}

void vm_emu_xorwf_iorwf(void *dev, vm_opcode * opcode, char i)
{
	char w;
	char f;

	w = vm_getW(dev);
	f = vm_readRamExposed(dev, opcode->parse6op7f.f);

	if (i)
		w |= f;	
	else
		w ^= f;
		
	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, w, opcode->parse6op7f.d);
	vm_setZifZero(dev, w);
}

void vm_emu_subwf_addwf(void *dev, vm_opcode * opcode, char add)
{
	char w;
	char f;

	int temp;

	w = vm_getW(dev);
	f = vm_readRamExposed(dev, opcode->parse6op7f.f);

	if (add)
	{
		temp = w & 0x00FF;
		temp += f;
	
		if(temp > 0xFF)
		{
			vm_status_set_c(dev,1);
			vm_status_set_dc(dev,1);
		}
		else
		{
			vm_status_set_c(dev,0);
			vm_status_set_dc(dev,0);
		}
		f += w;
	}
	else
	{
		if(f > w)
		{
			vm_status_set_c(dev,1);
			vm_status_set_dc(dev,1);
		}
		else
		{
			vm_status_set_c(dev,0);
			vm_status_set_dc(dev,0);
		}
		f -= w;
	}
	
	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, f, opcode->parse6op7f.d);
	vm_setZifZero(dev, f);	
}

void vm_emu_andwf(void *dev, vm_opcode * opcode)
{
	char res;

	res = vm_getW(dev);
	res &= vm_readRamExposed(dev, opcode->parse6op7f.f);

	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, res, opcode->parse6op7f.d);
	vm_setZifZero(dev, res);	
}

void vm_emu_clrw_clrf(void *dev, vm_opcode * opcode)
{	
	/* I know its not an 6 OP bits and 7F bits. Its a 7/7. but using this 
		bitfield, I can know if the eight bit (the one thats distinguish between
		clrf and clrw) is 0 or 1, and if it is 1, F must be set to 0, otherwise W */
	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, 0, opcode->parse6op7f.d);	
	vm_status_set_z(dev, 1);
}

void vm_emu_incf_decf(void *dev, vm_opcode * opcode, char sum)
{
	char f;

	f = vm_readRamExposed(dev, opcode->parse6op7f.f);	

	if (sum)
		f++;
	else
		f--;

	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, f, opcode->parse6op7f.d);
	vm_setZifZero(dev, f);
}

void vm_emu_comf(void *dev, vm_opcode * opcode)
{
	char f;

	f = ~vm_readRamExposed(dev, opcode->parse6op7f.f);	

	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, f, opcode->parse6op7f.d);
	vm_setZifZero(dev, f);
}

void vm_emu_movf(void *dev, vm_opcode * opcode)
{
	char f;

	f = vm_readRamExposed(dev, opcode->parse6op7f.f);	

	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, f, opcode->parse6op7f.d);
	vm_setZifZero(dev, f);
}

void vm_emu_rlf_rrf(void *dev, vm_opcode * opcode, char r)
{
	char f;
	char c;

	f = vm_readRamExposed(dev, opcode->parse6op7f.f);	

	if (r)
	{
		c = vm_status_get_c(dev) << 7;
		vm_status_set_c(dev, f & 0x01);
		c |= (f & 0xFE) >> 1;		
	}
	else
	{
		c = vm_status_get_c(dev);
		vm_status_set_c(dev, (f & 0x80) >> 7);
		c |= (f & 0x7F) << 1;
	}
	
	vm_writeRamExposedWithW(dev, opcode->parse6op7f.f, f, opcode->parse6op7f.d);
	vm_setZifZero(dev, f);
}